package com.ruoyi.cms.post.service.impl;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.ruoyi.cms.post.domain.CmsPost;
import com.ruoyi.cms.post.domain.CmsPostTag;
import com.ruoyi.cms.post.domain.CmsPostType;
import com.ruoyi.cms.post.mapper.CmsPostMapper;
import com.ruoyi.cms.post.mapper.CmsPostTagMapper;
import com.ruoyi.cms.post.mapper.CmsPostTypeMapper;
import com.ruoyi.cms.post.service.ICmsPostService;
import com.ruoyi.cms.tag.domain.CmsTag;
import com.ruoyi.cms.tag.mapper.CmsTagMapper;
import com.ruoyi.cms.tag.service.ICmsTagService;
import com.ruoyi.cms.type.domain.CmsType;
import com.ruoyi.cms.type.mapper.CmsTypeMapper;
import com.ruoyi.cms.type.service.ICmsTypeService;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.http.HttpUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.ruoyi.common.constant.RabbitMqConstants.*;
import static com.ruoyi.common.constant.RedisConstants.POST_ID_KEY;
import static com.ruoyi.common.constant.RedisConstants.PREFIX;

/**
 * 帖子管理Service业务层处理
 *
 * @author toumakazusa
 * @since 1.0.0
 */
@Service
public class CmsPostServiceImpl implements ICmsPostService {

    @Autowired
    private CmsPostMapper cmsPostMapper;

    @Autowired
    private CmsPostTagMapper cmsPostTagMapper;

    @Autowired
    private CmsTagMapper cmsTagMapper;

    @Autowired
    private CmsPostTypeMapper cmsPostTypeMapper;

    @Autowired
    private CmsTypeMapper cmsTypeMapper;

    @Autowired
    private ICmsTypeService cmsTypeService;

    @Autowired
    private ICmsTagService cmsTagService;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private StringRedisTemplate redisTemplate;

    private static final Logger log = LoggerFactory.getLogger(HttpUtils.class);

    @Override
    public Long insertCmsPost(CmsPost cmsPost) {
        //1.插入帖子
        cmsPost.setCreateTime(DateUtils.getNowDate());
        cmsPostMapper.insertCmsPost(cmsPost);
        Long postId = cmsPost.getId();
        //2.插入帖子与标签的关联
        Long[] tagIds = cmsPost.getTagIds();
        if (tagIds != null && tagIds.length > 0) {
            List<CmsPostTag> postTagList = Stream.of(tagIds)
                    .map(tagId -> new CmsPostTag(tagId, postId)).collect(Collectors.toList());
            cmsPostTagMapper.batchPostTag(postTagList);
        }
        //3.插入帖子和分类的关联
        Long[] typeIds = cmsPost.getTypeIds();
        if (typeIds != null && typeIds.length > 0) {
            List<CmsPostType> postTypeList = Stream.of(typeIds)
                    .map(typeId -> new CmsPostType(typeId, postId)).collect(Collectors.toList());
            cmsPostTypeMapper.batchPostType(postTypeList);
        }
        return postId;
    }

    @Override
    public CmsPost selectCmsPostById(Long id) {
        CmsPost post = cmsPostMapper.selectCmsPostById(id);
        //查询标签列表,获取tag的id列表和详细信息
        CmsPostTag cpt = new CmsPostTag();
        cpt.setPostId(id);
        List<CmsPostTag> postTagList = cmsPostTagMapper.selectPostTagList(cpt);
        Long[] tagIds = postTagList.stream().map(CmsPostTag::getTagId).toArray(Long[]::new);
        if (tagIds != null && tagIds.length > 0) {
            List<CmsTag> tags = cmsTagMapper.selectCmsTagByIds(Arrays.asList(tagIds));
            post.setTagIds(tagIds);
            post.setTags(tags);
        }

        //查询分类列表,获取type的id列表和详细信息
        CmsPostType cpt2 = new CmsPostType();
        cpt2.setPostId(id);
        List<CmsPostType> postTypeList = cmsPostTypeMapper.selectPostTypeList(cpt2);
        Long[] typeIds = postTypeList.stream().map(CmsPostType::getTypeId).toArray(Long[]::new);
        if (typeIds != null && typeIds.length > 0) {
            List<CmsType> types = cmsTypeMapper.selectCmsTypeByIds(Arrays.asList(typeIds));
            post.setTypeIds(typeIds);
            post.setTypes(types);
        }
        return post;
    }

    @Override
    public int deleteCmsPostByIds(Long[] ids) {
        for (Long id : ids) {
            //清空帖子分类关联
            cmsPostTypeMapper.deletePostTypeByPostId(id);
            //清空帖子标签关联
            cmsPostTagMapper.deletePostTagByPostId(id);
        }
        return cmsPostMapper.deleteCmsPostByIds(ids);
    }

    @Override
    public int updateCmsPost(CmsPost cmsPost) {
        cmsPost.setUpdateTime(DateUtils.getNowDate());
        Long postId = cmsPost.getId();
        //清空帖子分类关联
        cmsPostTypeMapper.deletePostTypeByPostId(postId);
        //清空帖子标签关联
        cmsPostTagMapper.deletePostTagByPostId(postId);
        //新增帖子标签
        Long[] tagIds = cmsPost.getTagIds();
        if (tagIds != null && tagIds.length > 0) {
            List<CmsPostTag> postTagList = new ArrayList<>();
            for (Long tagId : tagIds) {
                CmsPostTag cmsPostTag = new CmsPostTag();
                cmsPostTag.setPostId(postId);
                cmsPostTag.setTagId(tagId);
                postTagList.add(cmsPostTag);
            }
            cmsPostTagMapper.batchPostTag(postTagList);
        }
        //新增帖子分类
        Long[] typeIds = cmsPost.getTypeIds();
        if (typeIds != null && typeIds.length > 0) {
            List<CmsPostType> postTypeList = new ArrayList<>();
            for (Long typeId : typeIds) {
                CmsPostType cmsPostType = new CmsPostType();
                cmsPostType.setPostId(postId);
                cmsPostType.setTypeId(typeId);
                postTypeList.add(cmsPostType);
            }
            cmsPostTypeMapper.batchPostType(postTypeList);
        }
        return cmsPostMapper.updateCmsPost(cmsPost);
    }

    /**
     * 查询推荐帖子列表
     */
    @Override
    public List<CmsPost> selectCmsPostListRecommend(CmsPost cmsPost) {
        List<CmsPost> cmsPostList = cmsPostMapper.selectCmsPostListRecommend(cmsPost);
        List<CmsPost> postList = PostListAddTypeAndTag(cmsPostList);
        return postList;
    }

    @Override
    public List<CmsPost> selectCmsPostListByTypeId(Long id) {
        List<CmsPost> cmsPostList = cmsPostMapper.selectCmsPostListByTypeId(id);
        List<CmsPost> postList = PostListAddTypeAndTag(cmsPostList);
        return postList;
    }

    @Override
    public List<CmsPost> selectCmsPostListByTagId(Long id) {
        List<CmsPost> cmsPostList = cmsPostMapper.selectCmsPostListByTagId(id);
        List<CmsPost> postList = PostListAddTypeAndTag(cmsPostList);
        return postList;
    }

    /**
     * 获取帖子详情, 并增加帖子阅读量(redis+rabbitmq)
     * @param id
     * @return
     */
    @Override
    public AjaxResult getInfoDetail(Long id) {

        AjaxResult ajax = AjaxResult.success();
        CmsType cmsType = new CmsType();
        CmsTag cmsTag = new CmsTag();
        ajax.put("types", cmsTypeService.selectCmsTypeList(cmsType));
        ajax.put("tags", cmsTagService.selectCmsTagList(cmsTag));
        if (StringUtils.isNotNull(id)) {
            CmsPost cmsPost = selectCmsPostById(id);
            ajax.put(AjaxResult.DATA_TAG, cmsPost);
            // 异步增加帖子的阅读量
            handlePostPageViewIncrement(id);
            // 将数据库的阅读量与redis中的阅读量相加,形成新的阅读量(阅读量要一定时间才会同步到数据库)
            Long size = redisTemplate.opsForHyperLogLog().size(POST_ID_KEY + cmsPost.getId());
            cmsPost.setViews(cmsPost.getViews() + size);
        }
        return ajax;
    }


    /**
     * 异步处理文章浏览量自增
     * 生产者
     *
     * @param postId
     */
    private void handlePostPageViewIncrement(Long postId) {
        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
        // 封装map并转化为Json格式
        HashMap<String, Object> map = new HashMap<>();
        String ipAddr = IpUtils.getIpAddr(request);
        // 此处的key应该换成静态常量才比较规范, 根据ip防止重复添加
        map.put("ip", ipAddr);
        map.put("postId", postId);

        // mq采用的是direct模式, 消费addArticlePageview()
        rabbitTemplate.convertAndSend(POST_EXCHANGE, POST_PAGEVIEW_ROUTING, JSONUtil.toJsonStr(map));
    }

    /**
     *  监听文章浏览量自增, 当键过期, 意味着我们需要将数据同步到数据库
     *
     *  当下面方法键过期(这里设置的为1小时),我们需要将其同步到数据库
     *  数据库同步方法,使用的监听器, 具体看KeyExpiredListener.class
     *
     *  消费者
     * @param mapStr
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = POST_PAGEVIEW_QUEUE),
            exchange = @Exchange(name = POST_EXCHANGE, type = ExchangeTypes.DIRECT),
            key = POST_PAGEVIEW_ROUTING
    ))
    private void addPostPageview(String mapStr) {
        Map map = JSONUtil.toBean(mapStr, Map.class);
        // 根据ip限制同一个用户在一段时间内只能增加一次阅读量
        String ipAddr = map.get("ip").toString();
        String postId = map.get("postId").toString();
        if (ObjectUtil.hasEmpty(ipAddr, postId)) {
            return;
        }
        //这里设置两个key, 是为了让设置了过期时间的key过期后,方便我们获取他的值(备份)
        String key = POST_ID_KEY + postId;
        String key_copy = PREFIX + POST_ID_KEY + postId;
        // 添加2次缓存
        Long status = redisTemplate.opsForHyperLogLog().add(key, ipAddr);
        // 设置过期时间
        redisTemplate.expire(key, 1, TimeUnit.HOURS);
        redisTemplate.opsForHyperLogLog().add(key_copy, ipAddr);
        if (status == 0) {
            log.info("该ip地址：{}重复访问文章id:{}", ipAddr, postId);
        }
    }

    /**
     * 更新帖子的阅读量
     * @param postId
     * @param increment 增量
     * @return
     */
    @Override
    public int updatePostPageview(String postId, Long increment) {
        return cmsPostMapper.updatePostPageview(postId, increment);
    }



    @Override
    public List<CmsPost> selectCmsPostList(CmsPost cmsPost) {
        List<CmsPost> cmsPostList = cmsPostMapper.selectCmsPostList(cmsPost);
        List<CmsPost> postList = PostListAddTypeAndTag(cmsPostList);
        return postList;
    }

    private List<CmsPost> PostListAddTypeAndTag(List<CmsPost> cmsPostList) {
        if (cmsPostList == null || cmsPostList.size() < 0) {
            return cmsPostList;
        }

        // 查所有标签
        List<CmsPostTag> postTagList = cmsPostTagMapper.selectPostTagList(new CmsPostTag());
        // 查所有分类
        List<CmsPostType> postTypeList = cmsPostTypeMapper.selectPostTypeList(new CmsPostType());

        for (CmsPost post : cmsPostList) {
            Long postId = post.getId();
            //查询标签列表
            List<Long> tagIds = postTagList.stream().filter(postTag -> postTag.getPostId() == postId).map(CmsPostTag::getTagId).collect(Collectors.toList());
            if (tagIds != null && tagIds.size() > 0) {
                List<CmsTag> cmsTagList = cmsTagMapper.selectCmsTagByIds(tagIds);
                post.setTags(cmsTagList);
            }
            //查询分类列表
            List<Long> typeIds = postTypeList.stream().filter(postType -> postType.getPostId() == postId).map(CmsPostType::getTypeId).collect(Collectors.toList());
            if (typeIds != null && typeIds.size() > 0) {
                List<CmsType> cmsTypeList = cmsTypeMapper.selectCmsTypeByIds(typeIds);
                post.setTypes(cmsTypeList);
            }
        }
        return cmsPostList;
    }

}
